"""
WSGI Middleware to create a APP client for blogging.
"""
import re, os, amara, dateutil, urlparse, posixpath, urllib
from mimetypes import guess_type
import httplib2

from pprint import pprint

# from selector import *
from wsgiappclient.wsgidispatcher import Dispatcher
from paste.request import parse_formvars, get_cookies
from paste.httpexceptions import *
from beaker.middleware import SessionMiddleware
from Ft.Xml.Xslt import Transform
from Ft.Xml.Xslt import Processor
from Ft.Xml import InputSource, CreateInputSource
from Ft.Xml import Domlette, Parse


SLUGCHARS = r'a-zA-Z0-9\-\_'
ATOM10_NS = u'http://www.w3.org/2005/Atom'
XHTML_NS = u'http://www.w3.org/1999/xhtml'
USER_NS = u'http://ionrock.org/ns/wsgiappclient/user'
ATOMTHR_EXT_NS = u'http://purl.org/syndication/thread/1.0'
ENCODING = 'UTF-8'
COMMON_PREFIXES = { u'atom': ATOM10_NS, u'xh': XHTML_NS, u'thr': ATOMTHR_EXT_NS }
AC_NS = u"http://ionrock.org/ns/app-client"


from wsgiappclient.utils import get_base_url, fixup_characters, setup_xslt_params
from wsgiappclient.appclient import AppClient
from wsgiappclient.clientatomentry import ClientAtomEntry
# from wsgiappclient.store.s3store import S3UserStore

from pkg_resources import resource_string


#from wsgiappclient.openidauth import OpenIDRequestMiddleware

class WsgiAppClientMiddleware(object):

    def __init__(self, app_conf, app=None):
        if not app_conf.get('base_url'):
            print "base_url must be provided in the configuration."
            import sys
            sys.exit(1)
        self.user_id = app_conf.get('test_openid', None)
        self.user_store = app_conf.get('user_store', None)
        self.view = AppClientRenderer()
        self.app_conf = app_conf
        # self.auth = OpenIDRequestMiddleware(trusted_root='http://localhost:8000/auth')
        # self.store = S3UserStore(app_conf)
        #self.s = Selector()
        self.s = Dispatcher()
        self.s.add('/{openid_user}/author', POST=self.post_author)
        self.s.add('/{openid_user}/author/', POST=self.post_author)
        self.s.add('/openid', GET=self.get_openid)
        self.s.add('/openid/', GET=self.get_openid)
        self.s.add('/{openid_user}/collection/{action}/', GET=self.get_collection)
        self.s.add('/{openid_user}/create', GET=self.get_create, POST=self.post_create)
        self.s.add('/{openid_user}/create/', GET=self.get_create, POST=self.post_create)
        self.s.add('/{openid_user}/edit', GET=self.get_edit, POST=self.post_edit)
        self.s.add('/{openid_user}/edit/', GET=self.get_edit, POST=self.post_edit)
        self.s.add('/static/{js:any}', GET=self.static)
        self.s.add('/{openid_user}/', GET=self.get_index, POST=self.post_index)
        self.s.add('/{openid_user}', GET=self.get_index, POST=self.post_index)
        self.s.add('/', self.get_openid)

    def get_user(self, user_id):
        return User(user_id, store_dir=self.user_store)
        
    def render(self, action, data, xml=None, xslt=None, errors={}):
        #print 'in render'
        if not xml :
            xml = '%s/source.xml' % action
        if not xslt:
            xslt = '%s/forms.xslt' % action
        return self.view.render(xml, xslt, data, errors)

    def __call__(self, environ, start_response):
        # print "in middleware call"
        #testing
        # set openid cookie
        is_static = environ['PATH_INFO'].startswith('/static')
        is_openid = environ['PATH_INFO'].startswith('/openid')
        if not is_static and not is_openid:
            cookies = get_cookies(environ)
            if cookies.get('openid') and cookies.get('guid'):
                user_id = cookies['openid'].value
                user_guid = cookies['guid'].value
            elif self.user_id:
                user_id = self.user_id
                user_guid = "2398792837429347"
            else:
                headers = [('location', urlparse.urljoin(self.app_conf['base_url'], 'openid'))]
                raise HTTPTemporaryRedirect(headers=headers)
            sess = get_session(environ)
            sess['user_id'] = user_id
            sess['guid'] = user_guid
            sess.save()
        return self.s(environ, start_response)

    def get_openid(self, environ, start_response):
        data = {
            'redirect_url' : self.app_conf['base_url'],
            'base_url' : self.app_conf['base_url'],
        }
        output = self.render('openid', data)
        start_response('200 OK', [('content-type', 'text/html')])
        return [output]

    def post_author(self, environ, start_response):
        sess = get_session(environ)
        post = parse_formvars(environ)
        user = self.get_user(sess['user_id'])
        user.add_author(post)

        sess['flash'] = "Author information updated"
        sess.save()
        headers = [
            ('request_method', 'GET'),
            ('location', self.app_conf['base_url']),
        ]
        raise HTTPTemporaryRedirect(headers=headers)

    def get_index(self, environ, start_response):
        sess = get_session(environ)
        data = environ.get('wsgiappclient.post_index_data', {})
        errors = environ.get('wsgiappclient.post_index_errors', {})
        data['flash'] = sess.get('flash', '')
        sess['flash'] = ''
        sess.save()
        data['base_url'] = self.app_conf['base_url']
        data['openid'] = sess['user_id']
        # user_xml = 'users/%s.xml' % (sess['user_id'])
        user = User(sess['user_id'], self.user_store)
        user_xml = user.get_xml()
        try:
            output = self.render('index', data, errors=errors, xml=user_xml)
        except Exception, e:
            print e
            output = e
        start_response('200 OK', [('content-type', 'text/html')])
        return [output]

    def post_index(self, environ, start_response):
        #print "in add_service"
        post = parse_formvars(environ)
        sess = get_session(environ)
        if post.get('action'):
            action = post['action']
            print "Have action: %s" % action
            if action == 'user-add-collections':
                user = self.get_user(sess['user_id'])
                collection_uris = post.getall('collection_urls')
                for c in collection_uris:
                    username = post.get('username', None)
                    password = post.get('password', None)
                    if c:
                        user.add_collection(c, username, password)
                        user.save()
            if post['action'] == 'service-get-collections':
                data = {
                    'service' : post['app_service'],
                    'username' : post['username'],
                    'password' : post['password'],
                    'xml_base' : post['app_service']
                }
                environ['wsgiappclient.post_index_data'] = data
                save_session(environ)
            return self.get_index(environ, start_response)
        return self.get_index(environ, start_response)

    def get_edit(self, environ, start_response):
        #print "in post_edit_forms"
        getvars = parse_formvars(environ)
        # print getvars
        sess = get_session(environ)
        if not getvars.get('entry_uri') or not getvars.get('collection_uri'):
            start_response('200 OK', [('content-type', 'text/plain')])
            return ['No entry edit url or collection urL provided.']

        collection_uri = getvars['collection_uri']
        entry_uri = getvars['entry_uri']
        user = self.get_user(sess['user_id'])
        client = user.get_client(collection_uri)

        #print getvars['entry_uri']
        headers, body = client.get_entry(entry_uri)
        if headers.get('status') == '200':
            try:
                doc = amara.parse(body, prefixes=COMMON_PREFIXES)
            except Exception, e:
                sess['flash'] = "Error loading the atom entry."
                save_session(environ)
                headers = [('location', '/')]
                raise HTTPTemporaryRedirect(headers=headers)
        else:
            raise get_exception(int(headers['status']))()
        # print body
        # pprint(headers)
        data = environ.get('wsgiappclient.post_edit_data', {})
        data['base_url'] = self.app_conf['base_url']
        data['openid'] = sess['user_id']
        data['entry_uri'] = entry_uri
        data['collection_uri'] = collection_uri
        data['flash'] = sess.get('flash', '')
        sess['flash'] = ''
        sess.save()
        start_response('200 OK', [('content-type', 'text/html')])
        output = self.render('edit', data, xml=doc.xml())
        return [output]

    def post_edit(self, environ, start_response):
        sess = get_session(environ)
        post = parse_formvars(environ)
        entry_uri = post.get('entry_uri', None)
        collection_uri = post.get('collection_uri', None)
        action = post.get('action', 'default')

        if not entry_uri or not collection_uri:
            raise HTTPExpectationFailed("Need entry_uri and collection_uri.")
        if action == 'default':
            return self.get_edit(environ, start_response)

        user = self.get_user(sess['user_id'])
        client = user.get_client(collection_uri)
        headers, content = client.get_entry(entry_uri)
        if headers.get('status') != '200':
            raise get_exception(int(headers['status']))()
        atom_entry = ClientAtomEntry(post)
        try:
            new_entry = atom_entry.edit(content, post)
        except Exception, e:
            sess['flash'] = str(e)
            sess.save()
            print "Exception caught!"
            print e
            return self.get_edit(environ, start_response)
        
        headers, body = client.put_entry(new_entry, entry_uri)
        #pprint(headers)

        if headers.get('status').startswith('20'):
            environ['wsgiappclient.post_edit_data'] = {'updated' : True}
            sess['flash'] = 'Entry updated'
            sess.save()
            return self.get_edit(environ, start_response)
        raise get_exception(int(headers['status']))()
    
    def get_create(self, environ, start_response):
        #print "in create"
        post = parse_formvars(environ)
        data = environ.get('wsgiappclient.post_create_data', {})
        if not post.get('collection_uri'):
            raise HTTPExpectationFailed("Need collection_uri.")
        data['collection_uri'] = post['collection_uri']
        data['base_url'] = self.app_conf['base_url']
        data['openid'] = get_session(environ)['user_id']
        output = self.render('create', data)
        start_response('200 OK', [('content-type', 'text/html')])
        return [output]

    def post_create(self, environ, start_response):
        sess = get_session(environ)
        post = parse_formvars(environ)
        collection_uri = post.get('collection_uri', None)
        user = self.get_user(sess['user_id'])
        client = user.get_client(collection_uri)
        
        data = {}
        atom_entry = ClientAtomEntry(post, author=user.get_author())
        entry_xml = atom_entry.create(post)
        errors = atom_entry.validate(entry_xml)
        if errors:
            for k, v in errors.items():
                data['err_%s' % k] = v
            for k, v in post.items():
                if 'atom_' in k:
                    data[k] = v
        else:
            headers, body = client.post_entry(collection_uri, entry_xml, post)
            if headers.get('status', '') == '201':
                data['location'] = headers['location']
            else:
                data['location'] = '%s - %s' % (headers, body)
                for k, v in post.items():
                    data[k] = v
        environ['wsgiappclient.post_create_data'] = data
        return self.get_create(environ, start_response)

    def get_collection(self, environ, start_response):
        #print "in get_collection"
        args = environ['wsgiorg.routing_args'][1]
        #print args
        post = parse_formvars(environ)
        sess = get_session(environ)
        
        if args.get('action'):
            action = args['action']
            if action == 'delete' and post.get('collection_uri'):
                collection = post['collection_uri']
                user = self.get_user(sess['user_id'])
                user.delete_collection(collection)
        new_loc = urlparse.urljoin(self.app_conf['base_url'], sess['user_id'])
        headers = [('location', new_loc)]
        start_response('303 See Other', headers)
        return ['redirecting...']

    def static(self, environ, start_response):
        fn = environ['wsgiorg.routing_args'][1]['js']
        # from pprint import pprint
        # pprint(environ)
        fn = posixpath.join('xslt/static', fn)
        #print "Trying to get: %s" % fn
        try:
            # fn = file(self.sf.fget(fn), 'rb').read()
            ctype = guess_type(fn)[0]
            fn = resource_string(__name__, fn)
            if not ctype:
                ctype = "text/xml"
            start_response('200 OK', [('content-type', ctype)])
            return [fn]
        except IOError, e:
            start_response('404 Not Found', [('content-type', 'text/plain')])
            error = '"%s" not found\n\n%s' % (fn, e)
            return [error,]


class User(object):

    def __init__(self, user_id, store_dir='users', file_extension='xml'):
        self.prefixes = COMMON_PREFIXES
        self.prefixes[u'user'] = USER_NS
        self.store_dir = store_dir
        if not os.path.isdir(self.store_dir):
            os.makedirs(self.store_dir)
        self.file_extension = file_extension
        self.user_id = user_id
        self.user_file = '%s.%s' % (self.user_id, self.file_extension)
        self.user_file_path = os.path.join(self.store_dir, self.user_file)
        if os.path.exists(self.user_file_path):
            self.doc = amara.parse(self.user_file_path, prefixes=self.prefixes)
        else:
            self.doc = amara.create_document(u'user', USER_NS, prefixes=COMMON_PREFIXES)
            self.doc.user.xml_append(
                self.doc.xml_create_element(u'openid', content=unicode(self.user_id)))
            self.add_author({'atom_author_name' : user_id})
            self.save()

    def get(self):
        return self.doc

    def get_author(self):
        author = self.doc.xml_xpath('//atom:author')
        if author:
            return author[0].xml()
        else:
            return "<author><name>%s</name></author>" % self.user_id

    def add_author(self, data):
        if not self.doc.xml_xpath('//atom:author'):
            author = self.doc.xml_create_element(
                u'author', ATOM10_NS, content=unicode(self.user_id))
            author.xml_append(self.doc.xml_create_element(u'name', ATOM10_NS))
            author.xml_append(self.doc.xml_create_element(u'email', ATOM10_NS))
            author.xml_append(self.doc.xml_create_element(u'uri', ATOM10_NS))
            self.doc.user.xml_append(author)
            
        if data.get('atom_author_name'):
            if hasattr(self.doc.user.author, 'name'):
                name = self.doc.user.author.name
                self.doc.user.author.xml_remove_child(name)
            self.doc.user.author.xml_append(
                self.doc.user.author.xml_create_element(
                    u'name', ATOM10_NS, content=unicode(data['atom_author_name'])))

        if data.get('atom_author_email'):
            if hasattr(self.doc.user.author, 'email'):
                email = self.doc.user.author.email
                self.doc.user.author.xml_remove_child(email)
            self.doc.user.author.xml_append(
                self.doc.user.author.xml_create_element(
                    u'email', ATOM10_NS, content=unicode(data['atom_author_email'])))

        if data.get('atom_author_uri'):
            if hasattr(self.doc.user.author, 'uri'):
                uri = self.doc.user.author.uri
                self.doc.user.author.xml_remove_child(uri)
            self.doc.user.author.xml_append(
                self.doc.user.author.xml_create_element(
                    u'uri', ATOM10_NS, content=unicode(data['atom_author_uri'])))

        self.save()
        
    def get_xml(self):
        return self.doc.xml()

    def get_client(self, url):
        collection = self.get_collection(url)
        if hasattr(collection, 'username') and hasattr(collection, 'password'):
            client = AppClient(username=collection.username, password=collection.password)
        else:
            client = AppClient()
        return client

    def get_collection(self, url):
        if hasattr(self.doc.user, 'collection'):
            for c in self.doc.user.collection:
                if str(c.href) == url:
                    return c
        return None

    def add_collection(self, url, username=None, password=None):
        if not self.get_collection(url):
            attrs = { u'href' : unicode(url) }
            if username:
                attrs[u'username'] = unicode(username)
            if password:
                attrs[u'password'] = unicode(password)
            collection = self.doc.xml_create_element(u'collection', USER_NS, attributes=attrs)
            self.doc.user.xml_append(collection)
            self.save()

    def delete_collection(self, url):
        for i in range(0, len(self.doc.user.collection)):
            cur_href = str(self.doc.user.collection[i].href)
            if cur_href == url:
                cur_collection = self.doc.user.collection[i]
                self.doc.user.xml_remove_child(cur_collection)
                break
        self.save()

    def save(self):
        f = open(self.user_file_path, 'w+')
        self.doc.xml(f, indent=True)
        f.close()

import wsgiappclient.xsltextensions as xslext

class AppClientRenderer(object):

    def __init__(self):
        self.rs = '%s.xslt'
        self.proc = Processor.Processor()
        self.proc.registerExtensionFunction(
            u'http://ionrock.org/ns/http-document',
            u'get-http-document', xslext.get_http_document)
        self.proc.registerExtensionFunction(
            u'http://ionrock.org/ns/http-document',
            u'urljoin', xslext.urljoin)

    def get(self, fn):
        try:
            f = resource_string(__name__, 'xslt/%s' % fn)
            return f
        except Exception, e:
            return fn
        
    def run(self, xml, xslt, params):
        xml = CreateInputSource(xml, ATOM10_NS)
        xslt = CreateInputSource(xslt)
        self.proc.appendStylesheet(xslt)
        # pprint(params)
        out = self.proc.run(xml, topLevelParams=params)
        self.proc.reset()
        return out

    def do_render(self, xml, xslt, params):
        params['check_params'] = "Yup they are working!"
        nodes = {}
        for k, v in params.items():
            if isinstance(v, list):
                nodes[k] = v
        params = setup_xslt_params(AC_NS, params)
        for k, v in nodes.items():
            params[(AC_NS, k)] = v
        return self.run(xml, xslt, params=params)

    def render(self, xml, xslt, params={}, errors={}):
        if len(errors):
            for k, v in errors.items():
                params['err_%s' % k] = v
        xslt = self.get(xslt)
        xml = self.get(xml)
        return self.do_render(xml, xslt, params)
    
def get_session(environ):
    return environ['beaker.session']

def save_session(environ):
    environ['beaker.session'].save()


"""
This function defines the WSGI entry point for this application.
"""
def make_app(global_conf, **app_conf):
    app = WsgiAppClientMiddleware(app_conf)
    app = SessionMiddleware(app, key="wsigappclient.session", secret="ionrocks")
    return app

